﻿using System;
using System.Collections.Generic;
using System.Globalization;
using MentalAlchemy.Atomics;
using MentalAlchemy.Molecules.MachineLearning;

namespace MentalAlchemy.Molecules.NevaAlgorithm.nevamod
{
	public class NevaIndChanges
	{
		public string OpName;
		public Fitness FitDelta;
		public int Generation;

		public override string ToString()
		{
			return OpName + "\t" + FitDelta + "\t" + Generation;
		}

		public static NevaIndChanges FromString (string str)
		{
			var res = new NevaIndChanges();
			try
			{
				var substr = str.Split('\t');
				res.OpName = substr[0];
				res.FitDelta = new Fitness(float.Parse(substr[1]));
				res.Generation = int.Parse(substr[2]);
			}
			catch{}
			return res;
		}
	}

	public class FitnessDeltaStats
	{
		public Fitness SumTotal;
		public Fitness SumPositive;
		public Fitness SumNegative;
		public Fitness CountPositive;
		public Fitness CountNegative;
		public Fitness CountNeutrals;
	}

	public class Operators
	{
		public const string XOVER_NAME = "NevaCrossover";
		public const string ADD_NODE_MUTATION_NAME = "NevaAddNodeMutation";
		public const string DELETE_NODE_MUTATION_NAME = "NevaDeleteNodeMutation";
		public const string ADD_CONNECTION_MUTATION_NAME = "NevaAddConnectionMutation";
		public const string DELETE_CONNECTION_MUTATION_NAME = "NevaDeleteConnectionMutation";
		public const string NODE_ACTIVATION_MUTATION_NAME = "NevaNodeActivationMutation";
		public const string WEIGHT_MUTATION_NAME = "NevaWeightMutation";

		public const int MUTATION_TYPE_COUNT = 6;
		public const int ADD_NODE_MUTATION_ID = 0;
		public const int DELETE_NODE_MUTATION_ID = 1;
		public const int ADD_CONNECTION_MUTATION_ID = 2;
		public const int DELETE_CONNECTION_MUTATION_ID = 3;
		public const int NODE_ACTIVATION_MUTATION_ID = 4;
		public const int WEIGHT_MUTATION_ID = 5;

		public static string[] GetOperators ()
		{
			var res = new string[MUTATION_TYPE_COUNT + 1];	// structure and weight mutations + xover.
			res[0] = ADD_NODE_MUTATION_NAME;
			res[1] = DELETE_NODE_MUTATION_NAME;
			res[2] = ADD_CONNECTION_MUTATION_NAME;
			res[3] = DELETE_CONNECTION_MUTATION_NAME;
			res[4] = NODE_ACTIVATION_MUTATION_NAME;
			res[5] = WEIGHT_MUTATION_NAME;
			res[6] = XOVER_NAME;
			return res;
		}

		public static string[] GetMutOperators()
		{
			var res = new string[MUTATION_TYPE_COUNT];	// structure and weight mutations + xover.
			res[0] = ADD_NODE_MUTATION_NAME;
			res[1] = DELETE_NODE_MUTATION_NAME;
			res[2] = ADD_CONNECTION_MUTATION_NAME;
			res[3] = DELETE_CONNECTION_MUTATION_NAME;
			res[4] = NODE_ACTIVATION_MUTATION_NAME;
			res[5] = WEIGHT_MUTATION_NAME;
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Returns index of the mutation operator by its name.
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public static int GetMutationOperatorIndex (string name)
		{
			if (string.Compare(name, ADD_NODE_MUTATION_NAME, true) == 0) return ADD_NODE_MUTATION_ID;
			if (string.Compare(name, DELETE_NODE_MUTATION_NAME, true) == 0) return DELETE_NODE_MUTATION_ID;
			if (string.Compare(name, ADD_CONNECTION_MUTATION_NAME, true) == 0) return ADD_CONNECTION_MUTATION_ID;
			if (string.Compare(name, DELETE_CONNECTION_MUTATION_NAME, true) == 0) return DELETE_CONNECTION_MUTATION_ID;
			if (string.Compare(name, NODE_ACTIVATION_MUTATION_NAME, true) == 0) return NODE_ACTIVATION_MUTATION_ID;
			if (string.Compare(name, WEIGHT_MUTATION_NAME, true) == 0) return WEIGHT_MUTATION_ID;

			throw new Exception("[GetMutationOperatorIndex]: Unknown mutation operator name.");
		}
	}

	public class Neva2Elements
	{
		#region - Population generation. -
		/// <summary>
		/// Creates population using the given NEvA parameters.
		/// </summary>
		/// <param name="pars"></param>
		/// <returns></returns>
		public static List<AbstractIndividual> CreatePopulation(NevaParameters pars)
		{
			var res = new List<AbstractIndividual>();
			for (int i = 0; i < pars.PopulationSize; ++i)
			{
				res.Add(new Neva2Ind(pars.FitnessFunction.InputIds, pars.FitnessFunction.OutputIds, pars.GeneValueRange, pars.MinGeneValue, pars.RNG));
			}
			return res;
		}
		#endregion

		#region - Crossing. -
		/// <summary>
		/// [molecule]
		/// 
		/// Crosses given parent pair to produce offspring.
		/// </summary>
		/// <param name="parent1"></param>
		/// <param name="parent2"></param>
		/// <param name="pars"></param>
		/// <returns></returns>
		public static List<Neva2Ind> Cross(Neva2Ind parent1, Neva2Ind parent2, NevaParameters pars)
		{
			NevaInd c1, c2;
			NevaElements.Cross(parent1, parent2, out c1, out c2, pars);

			// evaluate child individuals.
			c1.Network.BuildNetwork(c1.Edges);
			c1.Fitness = pars.FitnessFunction.Calculate(c1.Network);
			c2.Network.BuildNetwork(c2.Edges);
			c2.Fitness = pars.FitnessFunction.Calculate(c2.Network);

			var tempCh1 = new NevaIndChanges();
			tempCh1.FitDelta = c1.Fitness - parent1.Fitness;
			tempCh1.Generation = pars.Algorithm.Contents.GenerationNumber;
			tempCh1.OpName = Operators.XOVER_NAME;

			var tempCh2 = new NevaIndChanges();
			tempCh2.FitDelta = c2.Fitness - parent2.Fitness;
			tempCh2.Generation = pars.Algorithm.Contents.GenerationNumber;
			tempCh2.OpName = Operators.XOVER_NAME;

			// create res array.
			var res = new List<Neva2Ind>();
			res.Add(Neva2Ind.Create(c1));
			res.Add(Neva2Ind.Create(c2));

			// register changes.
			res[0].Changes = new List<NevaIndChanges>(parent1.Changes);
			res[0].Changes.Add(tempCh1);
			res[1].Changes = new List<NevaIndChanges>(parent2.Changes);
			res[1].Changes.Add(tempCh2);

			var cont = (Neva2Contents)pars.Algorithm.Contents;
			cont.MutationCounters[tempCh1.OpName]++;

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Crossing with competition between parent and child individuals.
		/// </summary>
		/// <param name="selPopul"></param>
		/// <param name="pars"></param>
		/// <returns></returns>
		public static List<AbstractIndividual> Cross(List<AbstractIndividual> selPopul, NevaParameters pars)
		{
			var res = new List<AbstractIndividual>();
			if (pars.XRate == 0f)
			{	// do nothing if no crossing is performed.
				foreach (var ind in selPopul)
				{
					res.Add(ind.Clone());
				}
				return res;
			}


			//
			// todo: implement dynamic sizing (do I really need it?).
			//

			var selSize = selPopul.Count / 2;
			for (int i = 0; i < selSize; i++)
			{
				if (pars.XRate > pars.RNG.NextDouble())
				{
					//
					// select parent individuals.
					int p1 = i;
					int p2;
					int countTrials = 0;
					do
					{
						p2 = (int)(selSize * pars.RNG.NextDouble());
						countTrials++;
						if (countTrials > selPopul.Count) break;
					} while (p2 == p1 || ((NevaInd)selPopul[p1]).EqualsTo((NevaInd)selPopul[p2]));

					if (countTrials > selPopul.Count) break;

					//
					// create children.
					Neva2Ind parent1 = (Neva2Ind)selPopul[p1],
							parent2 = (Neva2Ind)selPopul[p2];
					var children = Cross(parent1, parent2, pars);
					Neva2Ind child1 = children[0], child2 = children[1];

					// nano-competition.
					if (pars.UseParentChildCompetition)
					{
						res.Add(FitnessComparator.IsBetter(child1.Fitness, parent1.Fitness) ? child1 : parent1);
						res.Add(FitnessComparator.IsBetter(child2.Fitness, parent2.Fitness) ? child2 : parent2);
					}
					else
					{
						res.Add(child1);
						res.Add(child2);
					}
				}	// end of if (CrossoverRate > QRandN())
			}	// end of for (i=0; i<SelectCount; i++)

			if (res.Count < selPopul.Count)
			{	// add mutated individuals.
				while (res.Count < selPopul.Count)
				{
					var i = (int)(selSize * pars.RNG.NextDouble());
					//var tempInd = (Neva2Ind)selPopul[i];
					var tempInd = Neva2Ind.Create( NevaElements.MutateWeight((Neva2Ind)selPopul[i], pars) );
					tempInd.Network.BuildNetwork(tempInd.Edges);
					tempInd.Fitness = pars.FitnessFunction.Calculate(tempInd.Network);

					// register change.
					var ch = new NevaIndChanges();
					ch.FitDelta = tempInd.Fitness - selPopul[i].Fitness;
					ch.Generation = pars.Algorithm.Contents.GenerationNumber;
					ch.OpName = Operators.WEIGHT_MUTATION_NAME;
					tempInd.Changes.Add(ch);

					if (pars.UseParentChildCompetition)
					{
						res.Add(FitnessComparator.IsBetter(tempInd.Fitness, selPopul[i].Fitness)? tempInd : selPopul[i]);
					}
					else
					{
						res.Add(tempInd);
					}
				}
			}
			else
			{	// remove extra individuals.
				res.RemoveRange(selPopul.Count, selPopul.Count - res.Count);
			}

			//if (EAElements.TimeFliesLikeArrow(res) > pars.AgeLimit)
			//{	// restart: the population is too old.
			//    res = NevaElements.CreatePopulation(pars);
			//}

			return res;
		}
		#endregion

		#region - Mutation. -
		public static List<AbstractIndividual> Mutate(List<AbstractIndividual> popul, NevaParameters pars)
		{
			var res = new List<AbstractIndividual>();
			var insouts = pars.FitnessFunction.InputIds.Count * pars.FitnessFunction.OutputIds.Count;
			for (int i = 0; i < popul.Count; i++)
			{
				if (popul[i].Size != 0)
				{
					var tempMRate = 1.0f / popul[i].Size;
					var nevaInd = (Neva2Ind)popul[i];
					var mut = new Neva2Ind(nevaInd);

					for (int j = 0; j < insouts; j++)
					{
						if (tempMRate > pars.RNG.NextDouble())
						{
							//LogWriter.Instance().WriteLine("MutateRandom " + i + "th individual");
							mut = MutateRandom(mut, pars);
						}
					}
					res.Add(mut);
				}
			}	// for (i=1, count=0; i<size; i++)
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Performs mutation using successfull transitions matrix.
		/// </summary>
		/// <param name="popul"></param>
		/// <param name="pars"></param>
		/// <param name="transM"></param>
		/// <returns></returns>
		public static List<AbstractIndividual> Mutate(List<AbstractIndividual> popul, NevaParameters pars, float[,] transM)
		{
			var res = new List<AbstractIndividual>();
			var insouts = pars.FitnessFunction.InputIds.Count * pars.FitnessFunction.OutputIds.Count;
			for (int i = 0; i < popul.Count; i++)
			{
				if (popul[i].Size != 0)
				{
					//var tempMRate = 1.0f / popul[i].Size;
					var tempMRate = 1.0f / insouts;
					var nevaInd = (Neva2Ind)popul[i];
					var mut = new Neva2Ind(nevaInd);

					for (int j = 0; j < insouts; j++)
					{
						if (tempMRate > pars.RNG.NextDouble())
						{
							// pick mutation operator.
							//mut = MutateRandom(mut, pars);
							//LogWriter.Instance().WriteLine("Mutate " + i + "th individual");
							mut = Mutate(mut, pars, transM);
						}
					}
					res.Add(mut);
				}
			}	// for (i=1, count=0; i<size; i++)

			//throw new NotImplementedException();
			return res;
		}

		/// <summary>
		/// [molecule]
		/// [deprecated]
		/// 
		/// Performs single mutation over given individual using specified algorithm parameters settings.
		/// </summary>
		/// <param name="ind">Individual to mutate.</param>
		/// <param name="pars">Algorithm parameters.</param>
		/// <returns>Mutated individual.</returns>
		public static Neva2Ind Mutate(Neva2Ind ind, NevaParameters pars)
		{
			var res = new Neva2Ind(ind);
			var ch = new NevaIndChanges();
			ch.Generation = pars.Algorithm.Contents.GenerationNumber;

			var cfactor = NevaElements.CalculateConnFactor(ind);
			bool disableNodeMutation = false;	// note this forced override !!!
			//bool disableNodeMutation = pars.Algorithm.Contents.NodesMutationLockCount > 0;

			//	if (QRandN() > factor || factor < 0.1) {
			if (pars.RNG.NextDouble() > cfactor)
			{
				if (pars.RNG.NextDouble() > cfactor)
				{
					res = Neva2Ind.Create(NevaElements.MutateAddConnection(ind, pars));
					ch.OpName = Operators.ADD_CONNECTION_MUTATION_NAME;
				}
				else
				{
					if (ind.Network.HiddenNodesCount > 0)
					{
						res = Neva2Ind.Create(!disableNodeMutation ? NevaElements.MutateDeleteNode(ind, pars) : NevaElements.MutateWeight(ind, pars));
						ch.OpName = Operators.DELETE_NODE_MUTATION_NAME;
					}
					else
					{
						res = Neva2Ind.Create(NevaElements.MutateAddConnection(ind, pars));
						ch.OpName = Operators.ADD_CONNECTION_MUTATION_NAME;
					}
				}
			}
			else
			{
				var nfactor = NevaElements.CalculateNodeFactor(ind);
				if (pars.RNG.NextDouble() > nfactor)
				{
					if ((pars.RNG.NextDouble() > cfactor) && ind.Network.HiddenNodesCount > 0)
					{
						res = Neva2Ind.Create(!disableNodeMutation ? NevaElements.MutateDeleteNode(ind, pars) : NevaElements.MutateWeight(ind, pars));
						ch.OpName = Operators.DELETE_NODE_MUTATION_NAME;
					}
					else
					{
						res = Neva2Ind.Create(NevaElements.MutateDeleteConnection(ind, pars));
						ch.OpName = Operators.DELETE_CONNECTION_MUTATION_NAME;
					}
				}
				else
				{
					res = Neva2Ind.Create(!disableNodeMutation ? NevaElements.MutateAddNode(ind, pars) : NevaElements.MutateWeight(ind, pars));
					ch.OpName = Operators.ADD_NODE_MUTATION_NAME;

					/*		An alternative variant: less speed, simpler networks
								if (QRandBool()) {
									mutateAddNeuron ();
								} else {
									mutateDeleteConnection ();
								}/**/
				}
			}	// end of if (QRandN() > (double)conns/(double)maxConns)
			res.Age = 0;

			// compute fitness.
			res.Fitness = pars.FitnessFunction.Calculate(res.Network);
			ch.FitDelta = res.Fitness - ind.Fitness;
			res.Changes.Add(ch);

			// nano-competition.
			if (pars.UseParentChildCompetition)
			{
				return FitnessComparator.IsBetter(res.Fitness, ind.Fitness) ? res : ind;
			}
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Performs individual mutation using successfull transitions matrix.
		/// </summary>
		/// <param name="ind"></param>
		/// <param name="pars"></param>
		/// <param name="transM"></param>
		/// <returns></returns>
		public static Neva2Ind Mutate(Neva2Ind ind, NevaParameters pars, float[,] transM)
		{
			int mutOpIdx;
			if (ind.LastMutation != "")
			{
				const float DEFAULT_WEIGHT = 0.1f;	// note this constant !!!

				// define mutation operator using previous mutation.
				var prevIdx = Operators.GetMutationOperatorIndex(ind.LastMutation);
				var row = MatrixMath.GetRow(transM, prevIdx);
				mutOpIdx = VectorMath.Roulette(row, DEFAULT_WEIGHT);
			}
			else
			{
				// if no previous mutation then select operator with largest average success rate.
				var sumRates = MatrixMath.SumRows(transM);
				mutOpIdx = VectorMath.Roulette(sumRates);
			}

			// perform mutation.
			switch (mutOpIdx)
			{
				case Operators.ADD_NODE_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.ADD_NODE_MUTATION_NAME);
					ind.LastMutation = Operators.ADD_NODE_MUTATION_NAME;
					return MutateAddNode(ind, pars);

				case Operators.DELETE_NODE_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.DELETE_NODE_MUTATION_NAME);
					ind.LastMutation = Operators.DELETE_NODE_MUTATION_NAME;
					return MutateDeleteNode(ind, pars);

				case Operators.ADD_CONNECTION_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.ADD_CONNECTION_MUTATION_NAME);
					ind.LastMutation = Operators.ADD_CONNECTION_MUTATION_NAME;
					return MutateAddConnection(ind, pars);

				case Operators.DELETE_CONNECTION_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.DELETE_CONNECTION_MUTATION_NAME);
					ind.LastMutation = Operators.DELETE_CONNECTION_MUTATION_NAME;
					return MutateDeleteConnection(ind, pars);

				case Operators.NODE_ACTIVATION_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.NODE_ACTIVATION_MUTATION_NAME);
					ind.LastMutation = Operators.NODE_ACTIVATION_MUTATION_NAME;
					return MutateChangeActivation(ind, pars);

				case Operators.WEIGHT_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.WEIGHT_MUTATION_NAME);
					ind.LastMutation = Operators.WEIGHT_MUTATION_NAME;
					return MutateWeight(ind, pars);

				default:
					LogWriter.Instance().WriteLine("Unknown mutation type.");
					throw new NotImplementedException("Unknown mutation type.");
			} 
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Performs single random mutation of the ANN structure for given individual.
		/// </summary>
		/// <param name="ind">Individual to mutate.</param>
		/// <param name="pars">Algorithm parameters.</param>
		/// <returns>Mutated individual.</returns>
		public static Neva2Ind MutateRandom(Neva2Ind ind, NevaParameters pars)
		{
			var mutType = ContextRandom.Next(Operators.MUTATION_TYPE_COUNT);
			switch (mutType)
			{
				case Operators.ADD_NODE_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.ADD_NODE_MUTATION_NAME);
					return MutateAddNode(ind, pars);

				case Operators.DELETE_NODE_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.DELETE_NODE_MUTATION_NAME);
					return MutateDeleteNode(ind, pars);

				case Operators.ADD_CONNECTION_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.ADD_CONNECTION_MUTATION_NAME);
					return MutateAddConnection(ind, pars);

				case Operators.DELETE_CONNECTION_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.DELETE_CONNECTION_MUTATION_NAME);
					return MutateDeleteConnection(ind, pars);

				case Operators.NODE_ACTIVATION_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.NODE_ACTIVATION_MUTATION_NAME);
					return MutateChangeActivation(ind, pars);

				case Operators.WEIGHT_MUTATION_ID:
					LogWriter.Instance().WriteLine(Operators.WEIGHT_MUTATION_NAME);
					return MutateWeight(ind, pars);

				default:
					throw new NotImplementedException("Unknown mutation type.");
			}
		}


		/// <summary>
		/// [molecule]
		/// 
		/// Adds connection to the given individual.
		/// </summary>
		/// <param name="ind"></param>
		/// <param name="pars"></param>
		/// <returns></returns>
		public static Neva2Ind MutateAddConnection (Neva2Ind ind, NevaParameters pars)
		{
			var res = new Neva2Ind((Neva2Ind)NevaElements.MutateAddConnection(ind, pars));

			// register changes.
			var ch = new NevaIndChanges();
			ch.Generation = pars.Algorithm.Contents.GenerationNumber;
			ch.OpName = Operators.ADD_CONNECTION_MUTATION_NAME;

			// register mutation.
			var cont = (Neva2Contents)pars.Algorithm.Contents;
			cont.MutationCounters[ch.OpName]++;

			// compute fitness.
			res.Fitness = pars.FitnessFunction.Calculate(res.Network);
			ch.FitDelta = res.Fitness - ind.Fitness;
			res.Changes.Add(ch);

			// nano-competition.
			if (pars.UseParentChildCompetition)
			{
				return FitnessComparator.IsBetter(res.Fitness, ind.Fitness) ? res : ind;
			}
			return res;
		}
		
		/// <summary>
		/// [molecule]
		/// 
		/// The most basic version of node activation's mutation.
		/// </summary>
		/// <param name="ind"></param>
		/// <param name="pars"></param>
		/// <returns></returns>
		public static Neva2Ind MutateChangeActivation (Neva2Ind ind, NevaParameters pars)
		{
			var res = (Neva2Ind)ind.Clone();

			// pick random node.
			var nonInputs = res.NonInputIndices;
			var idx = VectorMath.PickRandom(nonInputs);

			// pick random activation.
			var funcs = ActivationFunctions.Functions();
			var funcIdx = ContextRandom.Next(funcs.Length);
			res.ChangeActivation(idx, ActivationFunctions.GetActivationFunction (funcs[funcIdx]));
			//res.Network.GetNode(idx).ActivationFunction = ActivationFunctions.GetActivationFunction (funcs[funcIdx]);

			// register changes.
			var ch = new NevaIndChanges();
			ch.Generation = pars.Algorithm.Contents.GenerationNumber;
			ch.OpName = Operators.NODE_ACTIVATION_MUTATION_NAME;

			// register mutation.
			var cont = (Neva2Contents)pars.Algorithm.Contents;
			cont.MutationCounters[ch.OpName]++;

			// compute fitness.
			res.Fitness = pars.FitnessFunction.Calculate(res.Network);
			ch.FitDelta = res.Fitness - ind.Fitness;
			res.Changes.Add(ch);

			// nano-competition.
			if (pars.UseParentChildCompetition)
			{
				return FitnessComparator.IsBetter(res.Fitness, ind.Fitness) ? res : ind;
			}
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Weight mutation with fitness estimation and optional parent/child competition.
		/// </summary>
		/// <param name="ind"></param>
		/// <param name="pars"></param>
		/// <returns></returns>
		public static Neva2Ind MutateWeight (Neva2Ind ind, NevaParameters pars)
		{
			var tempMut = new Neva2Ind((Neva2Ind)NevaElements.MutateWeight(ind, pars));
			tempMut.Fitness = pars.FitnessFunction.Calculate(tempMut.Network);
			
			var ch = new NevaIndChanges();
			ch.OpName = Operators.WEIGHT_MUTATION_NAME;
			ch.FitDelta = tempMut.Fitness - ind.Fitness;
			ch.Generation = pars.Algorithm.Contents.GenerationNumber;
			tempMut.Changes.Add(ch);

			// register mutation.
			var cont = (Neva2Contents)pars.Algorithm.Contents;
			cont.MutationCounters[ch.OpName]++;

			// nano-competition.
			if (pars.UseParentChildCompetition)
			{
				return FitnessComparator.IsBetter(tempMut.Fitness, ind.Fitness) ? tempMut : ind;
			}
			return tempMut;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Removes node from the given individual.
		/// </summary>
		/// <param name="ind"></param>
		/// <param name="pars"></param>
		/// <returns></returns>
		public static Neva2Ind MutateDeleteNode (Neva2Ind ind, NevaParameters pars)
		{
			var ids = new List<int>(ind.NonInputIndices);
			var res = new Neva2Ind((Neva2Ind)NevaElements.MutateDeleteNode(ind, pars));

			var ch = new NevaIndChanges();
			ch.Generation = pars.Algorithm.Contents.GenerationNumber;
			ch.OpName = Operators.DELETE_NODE_MUTATION_NAME;

			// register mutation.
			var cont = (Neva2Contents)pars.Algorithm.Contents;
			cont.MutationCounters[ch.OpName]++;

			// register deleted node by removing its activation from the activations list.
			var newIds = new List<int>(ind.NonInputIndices);

			if (ids.Count == newIds.Count) { return ind; }	// no node removal took place so the individual wasn't changed.

			// compare old and new IDs and find out which node was deleted.
			foreach (var id in newIds)
			{
				if (ids.Contains(id))
				{
					ids.Remove(id);
				}
			}

			// the index that left is the index of the deleted node.
			res.Activations.Remove(ids[0]);

			// compute fitness.
			res.Fitness = pars.FitnessFunction.Calculate(res.Network);
			ch.FitDelta = res.Fitness - ind.Fitness;
			res.Changes.Add(ch);

			// nano-competition.
			if (pars.UseParentChildCompetition)
			{
				return FitnessComparator.IsBetter(res.Fitness, ind.Fitness) ? res : ind;
			}
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Adds node to the given individual.
		/// </summary>
		/// <param name="ind"></param>
		/// <param name="pars"></param>
		/// <returns></returns>
		public static Neva2Ind MutateAddNode(Neva2Ind ind, NevaParameters pars)
		{
			// If node is added without any connections then simply create clone of the current individual.
			// ???
			var res = pars.UseAddSingleNode ? new Neva2Ind(ind) : new Neva2Ind((Neva2Ind)NevaElements.MutateAddNode(ind, pars));

			var ch = new NevaIndChanges();
			ch.Generation = pars.Algorithm.Contents.GenerationNumber;
			ch.OpName = Operators.ADD_NODE_MUTATION_NAME;

			// register mutation.
			var cont = (Neva2Contents)pars.Algorithm.Contents;
			cont.MutationCounters[ch.OpName]++;

			// register newly added node by adding its activation into the activations list.
			var nodeId = res.GetMaxNodeIndex();	// index of the last added node.
			if (pars.UseAddSingleNode)
			{ // no node has yet been added.
				nodeId++;
				res.Activations.Add(nodeId, res.Network.DefaultActivationFunction);	// set default activation.
				res.Fitness = ind.Fitness;
			}
			else
			{	// new node is already there (with a couple of connections), just register it.
				res.Activations.Add(nodeId, res.Network.GetNode(nodeId).ActivationFunction);

				// compute fitness.
				res.Fitness = pars.FitnessFunction.Calculate(res.Network);
			}

			ch.FitDelta = res.Fitness - ind.Fitness;
			res.Changes.Add(ch);

			// nano-competition.
			if (pars.UseParentChildCompetition)
			{
				return FitnessComparator.IsBetter(res.Fitness, ind.Fitness) ? res : ind;
			}
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Tries to remove connection from the given individual.
		/// </summary>
		/// <param name="ind"></param>
		/// <param name="pars"></param>
		/// <returns></returns>
		public static Neva2Ind MutateDeleteConnection (Neva2Ind ind, NevaParameters pars)
		{
			var ids = new List<int>(ind.NonInputIndices);
			var res = new Neva2Ind((Neva2Ind)NevaElements.MutateDeleteConnection(ind, pars));
			var ch = new NevaIndChanges();
			ch.Generation = pars.Algorithm.Contents.GenerationNumber;
			ch.OpName = Operators.DELETE_CONNECTION_MUTATION_NAME;

			// register mutation.
			var cont = (Neva2Contents)pars.Algorithm.Contents;
			cont.MutationCounters[ch.OpName]++;

			// find out whether a node was deleted and if was then remove its activation from the activations list.
			var newIds = new List<int>(ind.NonInputIndices);
			if (ids.Count != newIds.Count)
			{
				// compare old and new IDs and find out which node was deleted.
				foreach (var id in newIds)
				{
					if (ids.Contains(id))
					{
						ids.Remove(id);
					}
				}

				// the index that left is the index of the deleted node.
				res.Activations.Remove(ids[0]);
			}

			// compute fitness.
			res.Fitness = pars.FitnessFunction.Calculate(res.Network);
			ch.FitDelta = res.Fitness - ind.Fitness;
			res.Changes.Add(ch);

			// nano-competition.
			if (pars.UseParentChildCompetition)
			{
				return FitnessComparator.IsBetter(res.Fitness, ind.Fitness) ? res : ind;
			}
			return res;
		}
		#endregion

		#region - Individual's conversion. -
		/// <summary>
		/// [molecule]
		/// 
		/// Convert given array of individual's changes into the array of strings.
		/// </summary>
		/// <param name="chs"></param>
		/// <returns></returns>
		public static List<string> ConvertToStrings (List<NevaIndChanges> chs)
		{
			var res = new List<string>();
			foreach (var ch in chs)
			{
				res.Add(ch.ToString());
			}
			return res;
		}
		#endregion

		#region - Multiple runs. -
		/// <summary>
		/// [molecule]
		/// 
		/// Performs multiple runs of the NEvA algorithms with specified parameters setting.
		/// </summary>
		/// <param name="pars">Parameters setting.</param>
		/// <param name="runsCount">Number of runs.</param>
		/// <param name="bests">List of best individual found at each run.</param>
		/// <returns>Averaged runs statistics.</returns>
		public static List<Stats> Run(NevaParameters pars, int runsCount, out List<Neva2Ind> bests)
		{
			var stats = new List<List<Stats>>();
			bests = new List<Neva2Ind>();
			for (int i = 0; i < runsCount; i++)
			{
				var neva = new Neva2();
				neva.Run(pars);
				stats.Add(neva.FitnessStats);
				bests.Add((Neva2Ind)neva.BestIndividual);
			}

			var avgStats = StructMath.Average(stats);
			return avgStats;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Performs multiple runs of the NEvA algorithms with specified parameters setting.
		/// </summary>
		/// <param name="pars">Parameters setting.</param>
		/// <param name="runsCount">Number of runs.</param>
		/// <param name="bests">List of best individual found at each run.</param>
		/// <param name="writeLog">Indicates whether log writing is enabled.</param>
		/// <returns>Averaged runs statistics.</returns>
		public static List<Stats> Run(NevaParameters pars, int runsCount, out List<Neva2Ind> bests, bool writeLog)
		{
			var stats = new List<List<Stats>>();
			bests = new List<Neva2Ind>();
			for (int i = 0; i < runsCount; i++)
			{
				var neva = new Neva2();
				neva.EnableLogging = writeLog;
				neva.Run(pars);
				stats.Add(neva.FitnessStats);
				bests.Add((Neva2Ind)neva.BestIndividual);
			}

			var avgStats = StructMath.Average(stats);
			return avgStats;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Performs multiple runs of the NEvA algorithms with specified parameters setting.
		/// </summary>
		/// <param name="pars">Parameters setting.</param>
		/// <param name="runsCount">Number of runs.</param>
		/// <param name="bests">List of best individual found at each run.</param>
		/// <param name="chs">List of changes of the best individuals found at each run.</param>
		/// <returns>Averaged runs statistics.</returns>
		public static List<Stats> Run(NevaParameters pars, int runsCount, out List<Neva2Ind> bests, out List<List<NevaIndChanges>> chs)
		{
			var stats = new List<List<Stats>>();
			bests = new List<Neva2Ind>();
			chs = new List<List<NevaIndChanges>>();
			for (int i = 0; i < runsCount; i++)
			{
				var neva = new Neva2();
				neva.Run(pars);
				stats.Add(neva.FitnessStats);
				bests.Add((Neva2Ind)neva.BestIndividual);
				chs.Add(((Neva2Ind)neva.BestIndividual).Changes);
			}

			var avgStats = StructMath.Average(stats);
			return avgStats;
		}
		#endregion
	}

	public class Neva2Contents : NevaContents
	{
		#region - Mutation counters. -
		public Dictionary<string, int> MutationCounters = new Dictionary<string, int>();
		#endregion

		public override void Init(NevaParameters pars)
		{
			popul = Neva2Elements.CreatePopulation(pars);
			bestInd = default(AbstractIndividual);	// reset best individual ...
			stats.Clear();	// ... and stats

			NodesMutationLockCount = 0;
			GenerationNumber = 0;

			FitnessFunction = pars.FitnessFunction;

			// init mutation counters.
			MutationCounters = new Dictionary<string, int>();
			var ops = Operators.GetOperators();
			foreach (var op in ops)
			{
				MutationCounters.Add(op, 0);
			}
		}
	}
}
